/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo
*/

/*! \file Утилита тестирования средств работы с JSON библиотеки SIMODO core. Проект SIMODO.
 */

#include "simodo/lsp-server/ServerContext.h"
#include "simodo/lsp-server/DocumentContext.h"

#include "simodo/lsp-client/LspEnums.h"

#include "simodo/variable/json/parser/JsonRdp.h"
#include "simodo/variable/json/parser/Json5Rdp.h"
#include "simodo/variable/json/parser/JsonLspBuilder.h"
#include "simodo/inout/log/Logger.h"
#include "simodo/inout/token/RefBufferStream.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/variable/json/Serialization.h"

#include <vector>
#include <memory>
#include <algorithm>
#include <exception>

using namespace simodo;

class JsonDocumentOperation : public lsp::DocumentOperation_interface
{
    lsp::DocumentContext &              _doc;

    std::vector<inout::Token>           _const_variable_set;
    std::vector<std::pair<inout::Token,inout::Token>>
                                        _group_set;

public:
    JsonDocumentOperation(lsp::DocumentContext &doc) : _doc(doc) {}

    virtual bool analyze(const std::u16string & text, inout::Reporter_abstract & m) override
    {
        if (_doc.file_name().empty())
            return false;

        std::vector<inout::Token>   const_variable_set;
        std::vector<std::pair<inout::Token,inout::Token>>
                                    group_set;
        variable::JsonLspBuilder    builder(const_variable_set, group_set);
        inout::RefBufferStream      buffer_stream(text.data());
        std::unique_ptr<inout::Parser_interface>   
                                    parser;
        variable::Value             value;
        bool                        ok = false;

        if (_doc.file_name()[_doc.file_name().size()-1] == '5')
            parser = std::make_unique<variable::Json5Rdp>(m, builder, _doc.file_name(), value);
        else
            parser = std::make_unique<variable::JsonRdp>(m, builder, _doc.file_name(), value);

        try
        {
            ok = parser->parse(buffer_stream);
        }
        catch(const std::exception & ex)
        {
            m.reportFatal(ex.what());
        }

        if (ok || const_variable_set.size() >= _const_variable_set.size()) {
            std::sort(group_set.begin(), 
                    group_set.end(), 
                    [](const std::pair<inout::Token,inout::Token> & a, const std::pair<inout::Token,inout::Token> & b)
                    {
                        return a.first.location().range().start() < b.first.location().range().start();
                    });

            _const_variable_set.swap(const_variable_set);
            _group_set.swap(group_set);
        }

        return true;
    }

    virtual bool checkDependency(const std::string & /*uri*/) const override
    {
        return false;
    }

    virtual variable::Value produceHoverResponse(const lsp::Position & /*pos*/) const override
    {
        return {};
    }

    virtual variable::Value produceGotoDeclarationResponse(const lsp::Position & /*pos*/) const override
    {
        return {};
    }

    virtual variable::Value produceGotoDefinitionResponse(const lsp::Position & /*pos*/) const override
    {
        return {};
    }

    virtual variable::Value produceCompletionResponse(const lsp::CompletionParams & /*completionParams*/) const override
    {
        return {};
    }

    virtual variable::Value produceSemanticTokensResponse() const override
    {
        struct TokenInfo { int64_t length, type, modifiers; };
        struct TokenComp {
            bool operator() (const std::pair<int64_t,int64_t> & x1, const std::pair<int64_t,int64_t> & x2) const {
                return x1.first < x2.first || (x1.first == x2.first && x1.second < x2.second);
            }
        };    

        std::map<std::pair<int64_t,int64_t>, TokenInfo, TokenComp> tokens;

        for(const inout::Token & t : _const_variable_set) 
            if (t.type() == inout::LexemeType::Id) {
                int64_t line      = static_cast<int64_t>(t.location().range().start().line());
                int64_t character = static_cast<int64_t>(t.location().range().start().character());
                int64_t length    = static_cast<int64_t>(t.location().range().end().character()-character);
                int64_t modifiers = static_cast<int64_t>(0);
                tokens.insert({ {line, character}, {length, 0, modifiers}});
            }

        std::vector<variable::Value> sem_tokens;

        for(const auto & [p,t] : tokens) {
            sem_tokens.emplace_back(p.first);
            sem_tokens.emplace_back(p.second);
            sem_tokens.emplace_back(t.length);
            sem_tokens.emplace_back(t.type);
            sem_tokens.emplace_back(t.modifiers);
        }
        
        return variable::Object {{{u"data", sem_tokens}}};
    }

    virtual variable::Value produceDocumentSymbolsResponse() const override
    {
        std::vector<variable::Value> doc_symbols;

        for(const inout::Token & t : _const_variable_set)
            if (t.type() == inout::LexemeType::Id) {
                    doc_symbols.emplace_back(variable::Object {{
                        {u"name",           t.token()},
                        {u"detail",         t.token()
                                            + u" [" + inout::toU16(std::to_string(t.location().range().start().line()+1))
                                            + u","  + inout::toU16(std::to_string(t.location().range().start().character()+1))
                                            + u"]"},
                        {u"kind",           int64_t(lsp::SymbolKind::Variable)},
                        {u"range",          _doc.makeRange({t.location().range().start(), t.location().range().end()})},
                        {u"selectionRange", _doc.makeRange(t.location().range())},
                    }});
                }

        return doc_symbols;
    }

    virtual variable::Value produceSimodoCommandResponse(const std::u16string & /*command_name*/, std::u16string /*text*/) const override
    {
        return {};
    }
};

class DocumentOperationFactory : public lsp::DocumentOperationFactory_interface
{
public:
    virtual std::unique_ptr<lsp::DocumentOperation_interface> create(lsp::DocumentContext &doc, const std::string & /*languageId*/)
    {
        return std::make_unique<JsonDocumentOperation>(doc);
    }
};

int main(int argc, char *argv[])
{
    inout::Logger::SeverityLevel  log_level = inout::Logger::SeverityLevel::Info;
    bool                          save_mode = false;
    std::unique_ptr<std::ostream> log_stream;
    std::unique_ptr<std::istream> in;

    if (argc > 1)
        log_stream = std::make_unique<std::ofstream>(argv[1]);

    if (argc > 2)
    {
        if (std::string(argv[2]) == "Debug")
            log_level = inout::Logger::SeverityLevel::Debug;
        else if (std::string(argv[2]) == "Info")
            log_level = inout::Logger::SeverityLevel::Info; // -V1048
        else if (std::string(argv[2]) == "Warning")
            log_level = inout::Logger::SeverityLevel::Warning;
        else if (std::string(argv[2]) == "Error")
            log_level = inout::Logger::SeverityLevel::Error;
        else if (std::string(argv[2]) == "Critical")
            log_level = inout::Logger::SeverityLevel::Critical;
    }

    if (argc > 3)
        save_mode = std::string(argv[3]) == "save";

    if (argc > 4)
#ifdef CROSS_WIN
        in = std::make_unique<std::ifstream>(simodo::inout::fromUtf8CharToWChar(argv[4]).c_str());
#else
        in = std::make_unique<std::ifstream>(argv[4]);
#endif
    inout::Logger log(log_stream ? *log_stream.get() : std::cerr, 
                    "SIMODO-LSP-JSON", 
                    log_level);

    lsp::ServerCapabilities server_capabilities;

    server_capabilities.hoverProvider = false;
    server_capabilities.documentSymbolProvider = true;
    server_capabilities.semanticTokensProvider.legend.tokenTypes = {u"property"};

    DocumentOperationFactory factory;

    lsp::ServerContext server(u"JSON",
                              server_capabilities,
                              lsp::SimodoCapabilities(),
                              factory,
                              log,
                              in ? *in.get() : std::cin,
                              std::cout,
                              save_mode);

    server.run();

    return 0;
}
